<?php


interface ICrontab
{

}

interface ITimer
{

	public function executed();


}

abstract class BTimer implements ITimer
{

	/** @var string 年 月 日 时 分 秒 */
	public $time = '* * * * * *';

	public $isLoop = false;

	public $isExecuted = false;

	public $lastExecTime;


	public $params = [];


	/**
	 * BTimer constructor.
	 */
	public function __construct()
	{
		$this->lastExecTime = $this->updateTime();
	}

	/**
	 * @return bool
	 */
	public function beforeExec()
	{
		$this->lastExecTime = $this->updateTime();
		return $this->executed();
	}


	public function reset($times)
	{
		foreach ($times as $key => $time) {
			if ($time == 0) {
				$this->lastExecTime = $times;
				break;
			}
		}
		return $times;
	}


	/**
	 * @return array
	 */
	private function updateTime()
	{
		$time = time();
		return $this->reset([
			date('Y', $time),
			date('m', $time),
			date('d', $time),
			date('H', $time),
			date('i', $time),
			date('s', $time)
		]);
	}

	/**
	 * @return bool
	 */
	public function canExec()
	{
		$date = date('Y m d H i s', time());
		if ($this->time === '* * * * * *') {
			return true;
		}
		$join = [];
		$replace = $this->updateTime();
		foreach (explode(' ', $this->time) as $key => $item) {
			if ($item == '*') {
				$join[] = $replace[$key];
			} else if (strpos($item, '/') !== false) {
				if (empty($this->lastExecTime)) {
					$this->lastExecTime = $this->updateTime();
					return false;
				}
				$explode = explode('/', $item)[1];

				$diff = $replace[$key] - $this->lastExecTime[$key];
				if ($diff) {
					$diff = -$diff;
				}
				if ($replace[$key] % $explode != 0) {
					return false;
//				} else if ($diff != $explode) {
//					echo $replace[$key] . ':' . $this->lastExecTime[$key] . ':' . $explode . PHP_EOL;
//					return false;
				}
				$join[] = $replace[$key];
			} else {
				$join[] = $item;
			}
		}
		if (implode(' ', $join) == $date) {
			return true;
		}
		return false;
	}

}

abstract class BCrontab implements ICrontab
{

	public $config = [];

	/**
	 * @return array
	 */
	protected function NewConfig()
	{
		$default = ArrayAccess::merge([
			'worker_num'      => 1,
			'reactor_num'     => 1,
			'task_worker_num' => 10,
			'dispatch_mode'   => 1,
			'daemonize'       => 0,
		], $this->config);
		return $default;
	}

}


class Timer extends BTimer
{
	/** @var string 年 月 日 时 分 秒 */
	public $time = '* * * * * *';

	/**
	 * @return bool
	 */
	public function executed()
	{
		echo 'Timer1: ' . date('Y-m-d H:i:s') . '.' . PHP_EOL;
		return true;
	}

}

class Timer2 extends BTimer
{
	/** @var string 年 月 日 时 分 秒 */
	public $time = '* * * * * */3';

	/**
	 * @return bool
	 */
	public function executed()
	{
		co::sleep(3);

		$client = new Swoole\Coroutine\Http\Client('test-api.zhuangb123.com', 80);
		$client->get('/generate');

		$o = 0;
		while (true) {
			sleep(1);

			$o++;
			if ($o >= 5) {
				break;
			}
		}

		var_dump($client->body);
		$client->close();

		echo 'Timer2: ' . date('Y-m-d H:i:s') . '.' . PHP_EOL;
		return true;
	}

}


class Crontab extends BCrontab
{

	/** @var \Swoole\Server $server */
	private $server;

	public $crontabs = [];

	public $command = 'listen:crontab:Task';
	public $description = '';


	public function DoCrontab()
	{
		$this->crontabs[] = new Timer();
		$this->crontabs[] = new Timer2();
		$this->server = new \Swoole\Server(SWOOLE_PROCESS);
		$this->server->set([
			'worker_num'            => 1,
			'reactor_num'           => 1,
			'task_worker_num'       => 10,
			'task_enable_coroutine' => true,
			'dispatch_mode'         => 1,
			'daemonize'             => 0,
		]);

		$this->server->on('workerStart', [$this, 'OnWorkStart']);
		$this->server->on('workerStop', function (\Swoole\Server $server, int $workId) {

		});
		$this->server->on('workerExit', function (\Swoole\Server $server, int $workId) {

		});
		$this->server->on('receive', [$this, 'OnReceive']);
		$this->server->on('Task', [$this, 'OnTask']);
		$this->server->on('finish', [$this, 'OnFinish']);
		$this->server->on('start', [$this, 'OnStart']);

		$this->server->start();
	}

	public function OnWorkStart(\Swoole\Server $server, int $worker_id)
	{
		if ($worker_id >= $server->setting['worker_num']) {
			return;
		}
		swoole_timer_tick(1000, [$this, 'loopHandler']);
	}

	public function OnStart(\Swoole\Server $server)
	{
		var_dump($server->master_pid);
	}

	public function handler()
	{
		$this->DoCrontab();
	}

	public function OnReceive(\Swoole\Server $server, int $fd, int $reactor_id, string $data)
	{

	}


	/**
	 * @param \Swoole\Server $server
	 * @param $task_id
	 * @param $from_id
	 * @param $data
	 * @return mixed
	 * @throws \Exception
	 */
	public function OnTask(\Swoole\Server $server, \Swoole\Server\Task $data)
	{
		$handler = unserialize($data->data);
		if (empty($handler) || !($handler instanceof ITimer)) {
			return $data->finish(json_encode([
				'handler'        => $handler,
				'message'        => 'Fail: Handler illegal.',
				'time_consuming' => 0,
				'result'         => false
			], JSON_UNESCAPED_UNICODE));
		}

		$logData = $this->format($handler);

		return $data->finish($logData);
	}


	/**
	 * @param $task_id
	 * @param $from_id
	 * @param Timer $handler
	 * @return string
	 * @throws \Exception
	 */
	public function format($handler)
	{
		$start = microtime(true);
		try {
			$handler->params = [0, 0];
			$result = $handler->beforeExec();
		} catch (\Exception $exception) {
			$message = $exception->getMessage();
			echo $message . PHP_EOL;
			$result = false;
		}
		return json_encode([
			'handler'        => $handler,
			'message'        => $message ?? 'success',
			'time_consuming' => microtime(true) - $start,
			'result'         => $result
		], JSON_UNESCAPED_UNICODE);
	}

	public function OnFinish($data)
	{
	}

	/**
	 *
	 */
	public function loopHandler()
	{
		/** @var Timer[] $seconds */
		foreach ($this->crontabs as $second) {
			if (!$second->canExec()) {
				continue;
			}
			$this->server->task(serialize($second));
		}
	}
}


$start = new Crontab();
$start->handler();
